package shark.oss.impl;

import io.minio.errors.*;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;
import shark.oss.OssFileService;
import shark.oss.OssStorageService;
import shark.oss.annotation.OssType;
import shark.oss.exception.OssException;
import shark.oss.model.ServerDefine;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class OssStorageServiceImpl implements OssStorageService {
    private static String ResourcePattern = "/**/*.class";
    private static String scanPath = "shark/**";

    private static Map<String,Class<? extends OssFileService>> ossFileClasses = loadOssFileClass();
    private Map<String, OssFileService> ossFileServiceMap = new HashMap<String, OssFileService>();

    public OssStorageServiceImpl(List<ServerDefine> serverDefines){
        for (ServerDefine serverDefine: serverDefines) {
            OssFileService service = buildFileAttachment(serverDefine);
            ossFileServiceMap.put(serverDefine.getName(),service);
        }
    }

    @Override
    public String persist(String server, InputStream inStream, String path, String fileName) throws OssException {
        OssFileService service = getOssFileServiceByName(server);

        return service.persist(server,inStream,path,fileName);
    }

    @Override
    public void update(String fileUrl, InputStream inStream) throws OssException {
        String serverName = getServerName(fileUrl);
        OssFileService service = getOssFileServiceByName(serverName);

        service.update(fileUrl,inStream);
    }

    @Override
    public void download(String fileUrl, HttpServletRequest request, HttpServletResponse response) throws OssException {
        String serverName = getServerName(fileUrl);
        OssFileService service = getOssFileServiceByName(serverName);

        service.download(fileUrl,request,response);
    }

    @Override
    public void delete(String fileUrl) throws OssException {
        String serverName = getServerName(fileUrl);
        OssFileService service = getOssFileServiceByName(serverName);

        service.delete(fileUrl);
    }

    @Override
    public boolean isExist(String fileUrl) throws OssException, ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        String serverName = getServerName(fileUrl);
        OssFileService service = getOssFileServiceByName(serverName);

        return service.isExist(fileUrl);
    }

    @Override
    public InputStream getFile(String fileUrl) throws OssException {
        String serverName = getServerName(fileUrl);
        OssFileService service = getOssFileServiceByName(serverName);

        return service.getFile(fileUrl);
    }

    @Override
    public List<String> listOssFileService() {
        List<String> list = new ArrayList<>();
        list.addAll(ossFileServiceMap.keySet());

        return list;
    }

    private static Map<String,Class<? extends OssFileService>> loadOssFileClass(){

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);

        String classPath = ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(scanPath));

        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + classPath + ResourcePattern;

        Map<String,Class<? extends OssFileService>> services = new HashMap<>();
        try {
            Resource[] resourceArray = resolver.getResources(packageSearchPath);

            for (Resource resource : resourceArray) {
                if (!resource.isReadable()) {
                    continue;
                }
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);

                try{
                    Class<?> class_ = Class.forName(sbd.getBeanClassName());

                    OssType annotation = class_.getAnnotation(OssType.class);
                    if (annotation != null) {
                        try {
                            Class<? extends OssFileService> attachmentClass = class_.asSubclass(OssFileService.class);
                            String serverType = annotation.value();
                            if (serverType == null || serverType.isEmpty()) {
                                serverType = class_.getSimpleName();
                            }
                            services.put(serverType, attachmentClass);
                        } catch (ClassCastException ex) {}
                    }
                }catch(ClassNotFoundException ex){}
            }
        }catch (IOException ex) {}

        return services;
    }

    private OssFileService buildFileAttachment(ServerDefine config) {
        Class<? extends OssFileService> ossFileClass = ossFileClasses.get(config.getType());

        OssFileService ossFileService = null;
        try {
            //获取参数为(String, Map)的构造方法
            Constructor<? extends OssFileService> dc = ossFileClass.getDeclaredConstructor(ServerDefine.class);
            //实例化文件服务对象
            ossFileService = dc.newInstance(config);
        } catch (Exception e) {
            throw new OssException("创建附件服务器失败, name=" + config.getName() +
                    ", type=" + config.getType() + " ossFileServiceClass=" + ossFileClass, e);
        }

        return ossFileService;
    }

    private OssFileService getOssFileServiceByName(String serverName) throws OssException{
        Assert.hasText(serverName, "服务器名称为空");

        OssFileService service = ossFileServiceMap.get(serverName);
        if(service==null){
            throw new OssException("指定的附件服务没有定义，附件服务名=" + serverName);
        }
        return service;
    }

    private OssFileService getAttachmentFileServiceByUrl(String fileUrl) throws OssException{
        Assert.hasText(fileUrl, "附件服务URL地址不可为空");

        return getOssFileServiceByName(getServerName(fileUrl));
    }

    private String getServerName(String fileUrl){
        Pattern serverRegex = Pattern.compile("^[^\\\\/:*?\"<>|\r\n]+?:");
        int index=0;

        Matcher regexMatcher = serverRegex.matcher(fileUrl);
        if (regexMatcher.find()) {
            index = regexMatcher.end();
        }
        if(index == 0){
            throw new OssException("附件url格式错误，fileUrl = " + (fileUrl == null ? "null" : fileUrl));
        }

        return fileUrl.substring(0, index-1);
    }
}
